<?php
	class Fmgr0v0r01__common extends _smuoiDbRowExtended implements Smuoi_Resource{
		
		protected $table = 'files';

		function read_only_keys(){
			static $a = array(
				'id','dir','created'
			);
			return $a;	
		}
	
		function get_functions(){
			static $a = array(
				'smrf',
				'href',
				'uri',
				'rdk',
				'path',
				'basename',
				'subname',
				'xtn',
				'name',
				'rel',
				'atag',
				'tag',
				'body',
				'meta',
				'parent',
				'mime_media',
				'mime_subtype',
				'filemtime',
				'fileatime',
				'filectime',
				'objitime',
				'deskpath',
				'is_readable',
				'is_writable',
				'is_executable',
				'is_writex',
				'mode',
				'perms',
				'rootdir',
				'summary',
				'icon'
			);
			return $a;
		}
		
		function set_functions(){
			static $a = array(
				'fmrp',
				'class',
				'parent',
				'basename',		
				'own',
				'grp',
				'mod',
				'body',
				'meta',
				'lstr',
				'lobj'
			);
			return $a;
		}
		
		function critical_defaults(){
			$t = time();
			return array(
				'dir' => $this->dir,
				'class' => $this->get_class(),
				'created' => $t,
				'updated' => $t
			);	
		}
		
		function seek_on_creation(){
			static $a = array(
				'body',
				'meta'
			);
			return $a;
		}
		
		function summary_vars(){
			static $a = array(
				'0:' => array(
					'id',
					'dir',
					'fmrp',
					'smrf',
					'basename',
					'class',
					'media_class',
					'mime',
					'name',
					'rel',
					'filesize', 
					'updated',
					'icon',
					'perms'	   
				),
				'1:' => array(
					'mode', 
					'own', 
					'grp', 
					'rdk', 
					'uri', 
					'deskpath', 
					'created', 
					'mime_media', 
					'checksum'
				),
				'2:' => array(
					'title', 
					'description',
					'tags',
					'body',
					'meta'
				)
			);
			return $a;
		}
		
		// set_functions must equate true or run_set_functions won't go
		protected $set_functions = true;
		
		protected $allow_read = false;
		protected $allow_write = false;
		protected $allow_execute = false;
		protected $cxt = 'w';
	
		protected $dir;
		protected $mod = 644;
		
		protected $parobj;
		
		protected $blobs;
		
		function __construct($id, $package, $data=false, $resolved=false){
			parent::__construct($id, $package, $data, $resolved);
			$this->read_permissions();
		}
		
		function get($k){
			if((!$this->allow_read) && (in_array($k, array('title','tags','description')))) return;
			return parent::get($k);
		}
		
		protected function initialize($o){
			$o = object($o);
			if(!isset($o->mod)) $o->mod = $this->mod;
			parent::initialize($o);
			$this->set($this->critical_defaults());
		}
		
		function is_read_only_key($k){
			return in_array($k, $this->read_only_keys());
		}
		
		function is_get_function($k){
			return in_array($k, $this->get_functions());
		}
		
		function is_set_function($k){
			return in_array($k, $this->set_functions());
		}
		
#### 	File permission functions
		
		protected function read_permissions(){
			$usr = usr();
			// first we must determine if we are in the context of user, group or world
			switch(true){
				// Simplest is to see first if user is false, i.e. world
				case !$usr:
					$this->cxt = 'w';
					break;
				// Is it root? Process root rules.
				case is_root():
					$this->cxt = 'R';
					$this->allow_read = true;
					$this->allow_write = $this->root_write();
					$this->allow_execute = $this->root_execute();
					return;
				// Is it you?
				case ($usr->id == $this->own()):
					$this->cxt = 'u';
					break;
				// Are they a member of our group?
				case (in_array($this->grp(), $usr->groups())):
					$this->cxt = 'g';
					break;
				// None of the above, it's world
				default:
					$this->cxt = 'w';
			}
			$p = $this->cxt_mode($this->cxt);
			// Read permissions are greater than 3
			$this->allow_read = ($p > 3);
			// write permissions are 2,3,6,7 
			$this->allow_write = in_array($p, array(2,3,6,7));
			// all odd numbers are execute permissions;
			$this->allow_execute = ($p &1);
		}
		
		// overridden by e.g. script files, sudoers may yet not have write permissions to some resources
		function root_write(){
			return true;
		}
		
		function root_execute(){
			// root can execute it if anyone can
			foreach($this->split_mode() as $m) if($m &1) return true;
		}
		
		// returns e.g. array(u=>7, g=>5, w=>5). Public.
		function read_mode(){
			return array_combine(array('u','g','w'), $this->split_mode());
		}
		
		function split_mode(){
			return str_split($this->mode());
		}
		
		function cxt_mode($c){
			$p = $this->read_mode();
			return $p[$c];
		}
		
		function perms(){
			return array((int)$this->allow_read, (int)$this->allow_write, (int)$this->allow_execute);
		}
		
		
####	get functions

		function parent(){
			if(is_null($this->parobj)) $this->parobj = $this->pkg->open($this->parent_fmrp());
			return $this->parobj;
		}
		
		function own(){
			return $this->data->own;
		}
		
		function grp(){
			return $this->data->grp;
		}
		
		function mode(){
			return $this->data->mod;
		}
		
		function rdk(){
			return substr($this->data->fmrp, 0, 4);
		}
		
		function href(){
			return substr($this->data->fmrp, 5);
		}
		
		function atag(){
			$s = ($this->data->title) ? $this->data->title : $this->name();
			return '<a href="'.$this->uri().'" title="'.htmlentities($s).'">'.$s.'</a>';	
		}
		
		function tag(){
			return $this->atag();	
		}
		
		function path(){
			return '/'.substr(($h = $this->href()), 1, (($y = strrpos($h, '/')) ? $y-1 : 0));	
		}
		
		function parent_fmrp(){
			return $this->rdk().':'.$this->path();
		}
		
		function php_fmrp(){
			return $this->rdk().':/'.$this->href();
		}

		function basename(){
			return basename($this->href());	
		}
		
		function xtn(){
			$b = $this->basename();
			while(substr($b, 0, 1) == '.') $b = substr($b, 1);
			return ($n = strrpos($b, '.')) ? substr($b, $n+1) : '';
		}
		
		function subname(){
			$b = $this->basename();
			return $this->xtn() ? substr($b, 0, strrpos($b, '.')) : $b;
		}
		
		// name : basename, typically
		function name(){
			return $this->basename();
		}
		
		// rootdir : string full path of root directory in this context
		function rootdir(){
			return $this->pkg->rootdir($this->rdk());
		}
		
		function realpath(){
			return $this->rootdir().$this->href();
		}
		
		function exists(){
			$this->hard_set('xst', (int)file_exists($this->realpath()));
			return $this->data->xst;
		}
		
		function is_readable(){
			return $this->allow_read;
		}
		
		function is_writable(){
			return $this->allow_write;
		}
		
		function is_executable(){
			return $this->allow_execute;
		}
		
		function is_writex(){
			return ($this->allow_write && $this->allow_execute);
		}
		
		// filemtime: should we need to call it seperately to date, let's ask the filesystem
		function filemtime(){
			return filemtime($this->realpath());
		}
		
		function filectime(){
			return filectime($this->realpath());	
		}
		
		function fileatime(){
			return fileatime($this->realpath());	
		}
		
		function objitime(){
			return base_convert($this->pkg->remove_key($this->id), 36, 10);
		}
		
		function rel(){
			return $this->basename();
		}
		
		function uri(){
			return $this->domain()->uri($this->rdk()).$this->href();
		}
			
		function smrf(){
			return $this->dom_id().'::'.$this->fmrp;
		}
				
		function deskpath(){
			switch($this->rdk()){
				case 'desk':
					return $this->href();
				case 'docs':
					return '/docroot'.$this->href();
			}
			return '';
		}
		
		function icon(){
			return $this->class.'.png';
		}
		
		function mime_media(){
			return array_shift(explode('/', $this->data->get('mime')));
		}
		
		function mime_subtype(){
			return array_pop(explode('/', $this->data->get('mime')));
		}
		
		// meta always returns an object
		function meta($create=false){
			return (!$this->allow_read || (!$m = $this->get_ltxt('lobj', $create))) ? new jso : $m;
		}
		
		// body check for the bool and returns an empty string if false
		function body(){
			if(!$this->allow_read) return;
			return ($b = $this->get_ltxt('lstr')) ? (string)$b : '';
		}
		
		protected function get_ltxt($k, $do=false){
			if(isset($this->blobs[$k])) return $this->blobs[$k]->blob();
			if((!$do && !$this->data->get($k)) || (!$blob = $this->pkg->Longtext()->retrieve_my($k, $this, $do))) return false;
			$this->edits[$k] = 1;
			$this->blobs[$k] = $blob;
			return $blob->blob();
		}
		
		function usurpa(){
			return $this;	
		}
		
		function summary($l=0){
			if(!$this->class_stable()){
				$this->reinstantiate();
				return $this->pkg->open($this->data->fmrp)->summary($l);
			}
			$l = !$this->allow_read ? 0 : (int)$l;
			$a = $this->summary_vars();
			$o = new obj;
			for($i=0; $i <= $l; $i++){
				if(!isset($a["$i:"])) break;
				foreach($a["$i:"] as $k){
					$v = $this->is_get_function($k) ? $this->$k() : $this->$k;
					if(smuoi_is_rsrc($v)) $v = $v->summary(0);
					$o->set($k, $v);
				}
			}
			return $o;
		}
		
		function duplicate(){
			return $this->copy_to($this->parent());
		}

####	set functions
		
		protected function set_fmrp(){
			return false;	
		}
		
		protected function set_class(){
			return false;	
		}
		
		protected function set_parent(Fmgr_Directory $newpar){
			if(!$newpar->is_writex()) return false;
			$samepar = false;
			if($xst = $this->exists()){
				$oldpar = $this->parent();
				// This was causing nasty failures when garnering orphaned files by checksum.
				// Having merged new data with old and passing to the child object, new parent would do $child->parent = $this; $child->commit(); 
				// but the following condition meant that child would not commit and so update its basename (or aything else) in the database
				//if($oldpar === $newpar) return false;
				if(!$oldpar->is_writex()) return false;
				$samepar = ($oldpar === $newpar);
				$cb = $this->basename();
			}
			if($newpar->is_local() != $this->is_local()){
				$this->copy_to($newpar);
				return false;	
			}
			// here we perform the same function as set_basename, but with intended par as referee
			if($samepar) $bn = $cb;
			else{
				if(!$bn = $newpar->permit_child_basename($this->build_basename())) return false;
				if($xst){
					if(!rename($this->realpath(), $newpar->realpath($bn))) return false;
					$oldpar->removeChild($this, $cb);
				}
			}
			// now we prevent a simultaneous call to set basename from running (since the intended basename has been used) 
			unset($this->edits['basename']);
			$this->update_fmrp($newpar->child_fmrp($bn));
			$this->parobj = $newpar;			
			if(!$xst) $this->exists();
			$this->commit_parent();
			$this->add_sanity_check('class');
			return true;
		}
		
		// update_fmrp is overwritten by dir who also updates its children in the database
		protected function update_fmrp($fmrp){
			$this->do_update_fmrp($fmrp);	
		}
		
		// do_update_fmrp is called by ourselves & super directories who are in the proccess of realigning their heirs
		protected function do_update_fmrp($fmrp){
			$this->pkg->uncache($this);	
			$this->hard_set('fmrp', $fmrp);
			$this->pkg->cache($this);
		}
		
		protected function set_basename($bn){
			if(($bn == $this->basename()) || (!$this->exists())) return false;
			$p = $this->parent();
			if(!$p->is_writex()) return false;
			if(!$bn = $p->permit_child_basename($this->build_basename())) return false;
			if(!rename($this->realpath(), $p->realpath($bn))) return false;
			$this->update_fmrp($p->child_fmrp($bn));
			$this->add_sanity_check('class');
			return true;
		}
		
		protected function set_own(SmRoot_User $o){
			if(!is_root()) return false;
			$this->hard_set('own', $o->id);
			return true;
		}
		
		protected function set_grp(SmRoot_group $o){
			if(!is_root()) return false;
			$this->hard_set('grp', $o->id);
			return true;
		}
		
		protected function set_mod($mod){
			$ma = str_split($mod);
			$mod = '';
			for($i=0; $i<3; $i++){
				$m = @$ma[$i];
				if(($n = (!is_numeric($m) || ($m < 0) || ($m > 7))) && !isset($ca)) $ca = str_split($this->mode());
				$mod .= ($n) ? $ca[$i] : $m;
			}
			$this->hard_set('mod', $mod);
			return true;
		}
		
		// set_meta and set_body put whole chunks of data into the object using set_ltxt, and edits[$k] becomes 1;
		protected function set_meta($d){
			return $this->set_ltxt('lobj', object($d));
		}
		
		protected function set_body($str){
			return $this->set_ltxt('lstr', $str);
		}
		
		protected function set_ltxt($k, $d){
			$this->get_ltxt($k, 1);
			$this->blobs[$k]->set_blob($d);
			return false;
		}
		
		// set_lstr and set_lstr are called privately on account of there being an edit
		protected function set_lstr(){
			return $this->blobs['lstr']->commit();
		}
		
		protected function set_lobj(){
			return $this->blobs['lobj']->commit();
		}	
		
####	Commital

		function commit(){
			$xst = $this->exists();
			if(!parent::commit(1)) return false;
			$xst ? $this->pkg->cache($this) : $this->get_forgot();
			return true;
		}
		
		function class_stable(){
			return ($this->data->class == $this->get_class());	
		}
		
		function get_forgot(){
			$this->pkg->uncache($this);
			$this->db()->remove($this->id);
		}
		
		function reinstantiate(){
			$this->get_forgot();
			$this->db()->register(get_object_vars($this->data), $this->table(), $this->resolved, 1);
		}
		
		function commit_parent(){
			$this->parent()->commit_on_shutdown();
		}
		
		function cold_settings(){
			return $this->pkg->read_coldfile($this->realpath());	
		}
		
		function touch(){
			touch($this->realpath());
			$this->add_sanity_check('filesize');
			$this->commit();
		}

		protected function prepare_commit(){
			if(!$this->allow_write) return false;
			if($this->new){
				foreach($this->seek_on_creation() as $k) if($v = $this->get_intended($k)) $this->edits[$k] = $v;
				if($this->edits) $this->run_set_functions();
				$this->commit_parent();
			}
			return parent::prepare_commit();
		}
		
		protected function run_sanity_checks(){
			parent::run_sanity_checks();
			$this->edits['updated'] = time();
			if($this->is_edited('filesize')) $this->commit_parent();
		}
		
		protected function is_trashable(){
			return true;
		}
		
		protected function destroy_row(){
			if($this->data->get('lobj') || $this->data->get('lstr')) $this->pkg->Longtext()->unlink($this->id);
			$this->db()->delete($this->id);
			$this->commit_parent();
			$this->pkg->uncache($this);
			return true;
		}
		
		function is_destroyable(){
			if(!$this->allow_write) return false;
			if(!$this->exists()) return true;
			return sm_beginswith($this->data->fmrp, 'desk:/trash/');
		}
		
		function destroy(){
			return false;	
		}
		
		function is_child_of($d){
			if(!$this->pkg->is_fmgr_dir($d)) return false;
			return sm_beginswith($this->data->fmrp, $d->fmrp.'/');
		}
		
		function __tostring(){
			return $this->php_fmrp();	
		}

	}


